/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package testes;

import java.util.ArrayList;
import java.util.Random;
import org.jgap.InvalidConfigurationException;
import org.jgap.gp.CommandGene;
import org.jgap.gp.GPFitnessFunction;
import org.jgap.gp.GPProblem;
import org.jgap.gp.IGPProgram;
import org.jgap.gp.function.Add;
import org.jgap.gp.function.Cosine;
import org.jgap.gp.function.Divide;
import org.jgap.gp.function.Exp;
import org.jgap.gp.function.Multiply;
import org.jgap.gp.function.Pow;
import org.jgap.gp.function.Sine;
import org.jgap.gp.function.Subtract;
import org.jgap.gp.impl.DeltaGPFitnessEvaluator;
import org.jgap.gp.impl.FitnessProportionateSelection;
import org.jgap.gp.impl.GPConfiguration;
import org.jgap.gp.impl.GPGenotype;
import org.jgap.gp.impl.GPPopulation;
import org.jgap.gp.impl.ProgramChromosome;
import org.jgap.gp.impl.TournamentSelector;
import org.jgap.gp.terminal.Terminal;
import org.jgap.gp.terminal.Variable;
import org.jgap.impl.WeightedRouletteSelector;

/**
 *
 * @author Heitor
 */
public class CurvaProblemv2 extends GPProblem {

    /*
     * Criação das variaveis, que vão ser usada como parametros da GP
     * 
     *
     */
    public static Float[] x = new Float[40];
    public static float[] y = new float[40];
    public static Float[] z;
    // number of variables to use (output variable is excluded)
    public static int numVariables;
    // the variables to use (of size numVariables)
    public static Variable[] variables;
    // variable name
    public static String[] variableNames;
    // index of the output variable
    public static Integer outputVariable; // default last
//    public static int[] ignoreVariables; // TODO
    // constants
    public static ArrayList<Double> constants = new ArrayList<Double>();
    // size of data
    public static int numRows;
    // the data (as Double)
    // Note: the last row is the output variable per default
    protected static Double[][] data;
    // If we have found a perfect solution.
    public static boolean foundBeastIndiv = false;
    // standard GP parameters
    public static int minInitDepth = 2;
    public static int maxInitDepth = 4;
    public static int populationSize = 1000;
    public static int maxCrossoverDepth = 8;
    public static int programCreationMaxTries = 5;
    public static int numEvolutions = 1800;
    public static boolean verboseOutput = true;
    public static int maxNodes = 50;
    public static double functionProb = 0.9d;
    public static float reproductionProb = 0.1f; // float
    public static float mutationProb = 0.01f; // float
    public static float crossoverProb = 0.9f; // float
    public static float dynamizeArityProb = 0.08f; // float
    public static double newChromsPercent = 0.3d;
    public static int tournamentSelectorSize = 0;
    public static boolean noCommandGeneCloning = true;
    public static boolean strictProgramCreation = false;
    public static boolean useProgramCache = true;
    // valores minimos e valores máximos para os terminais
    public static double minValue = 1.0;
    public static double maxValue = 12.0;
    // retornar apenas números inteiros (relevante apenas quando se usa tipo double ou float)
    public static boolean returnWholeNumbers = true;
    public static String returnType = "DoubleClass"; // not used yet
    public static String presentation = "";
//    // Using ADF
//    public static int adfArity = 0;
//    public static String adfType = "double";
//    public static boolean useADF = false;
    // list of functions (as strings)
    public static String[] functions = {"Multiply", "Divide", "Add", "Subtract"};
    // list of functions for ADF
    public static String[] adfFunctions = {"Multiply3", "Divide", "Add3", "Subtract"};
    // if the values are too small we may want to scale
    // the error
    public static double scaleError = -1.0d;
    // timing
    public static long startTime;
    public static long endTime;
    // if >= 0.0d, then stop if the fitness is below or equal
    // this value
    public static double stopCriteriaFitness = -1.0d;
    // shows the whole population (sorted by fitness)
    // in all generations. Mainly for debugging purposes.
    public static boolean showPopulation = false;
    // show similiar solutions with the same fitness
    // as the overall best program
    public static boolean showSimiliar = false;
    // 2010-02-27
    // how to sort the similiar solutions.
    // Valid options:
    //   - occurrence (descending, default)
    //   - length (asccending)
    public static String similiarSortMethod = "occurrence";
    // shows progression as generation number
    public static boolean showProgression = false;
    // show results for all generations
    public static boolean showAllGenerations = false;
    // show all the results for the fittest program
    public static boolean showResults = false;
    public static int resultPrecision = 5;
    // take a sample of the data set (if > 0.0)
    public static double samplePCT = 0.0d;
    // hits criteria: if >= 0.0d then collect and show
    // number of fitness values (errors) that is equal 
    // or below this value.
    public static double hitsCriteria = -1.0d;
    // withheld a percentage for validation of the
    // of the fittest program
    public static double validationPCT = 0.0d;
    protected static Double[][] validationSet;
    // for testing some values
    public static Double[][] testData;
    // for ModuloReplaceD: replacement for 0
    public static int modReplace = 0;
    // 2010-02-27
    // make time series based on a single sequence
    public static boolean makeTimeSeries = false;
    // 2010-02-27
    // make time series based on a single sequence
    // and adding the index of the instance
    public static boolean makeTimeSeriesWithIndex = false;
    // make a sequence of (ix, number) based on a single sequence
    // public static boolean makeIndexSeq = false;
    // 2010-02-22
    // Error method. 
    // Valid options: 
    //    totalError (default)
    //    minError
    //    meanError
    //    medianError
    //    maxError
    // 
    // Note that hitsCriteria > -1 overrides errorMethod
    // 
    public static String errorMethod = "totalError";
    // 2010-02-26: If we don't want to have any Terminals
    public static boolean noTerminals = false;
    // 2010-03-01: Penalty if the number of nodes in a program
    //             is less than minimum required.
    public static int minNodes = -1;
    //  The penalty for less nodes (may be application dependent)
    public static double minNodesPenalty = 0.0d;

    // 2010-03-02: Penalty if the variables are not different
//    public static boolean alldifferentVariables = false;
//    public static double alldifferentVariablesPenalty = 0.0d;
    public CurvaProblemv2(GPConfiguration a_conf) throws InvalidConfigurationException {
        super(a_conf);
    }

    /*
     * Este método é usado para configurar os comandos e os terminais 
     * que podem ser usados ​​para resolver o problema.
     */
    @Override
    public GPGenotype create() throws InvalidConfigurationException {

        GPConfiguration conf = getGPConfiguration();

        //primeiramente definimos os tipos de retornos da GP
        Class[] types = new Class[]{CommandGene.FloatClass};//somente 1 agumento pq não vou usar ADF
        Class[][] argTypes = new Class[][]{{}}; // vazio porque não vou usar adf

        //aki definimos o conjunto de comandos disponíveis GP.
        ArrayList<CommandGene> commandsList = new ArrayList<CommandGene>();
        commandsList.add(new Add(conf, CommandGene.FloatClass));
        commandsList.add(new Subtract(conf, CommandGene.FloatClass));
        commandsList.add(new Multiply(conf, CommandGene.FloatClass));
        commandsList.add(new Divide(conf, CommandGene.FloatClass));
        commandsList.add(new Sine(conf, CommandGene.FloatClass));
        commandsList.add(new Cosine(conf, CommandGene.FloatClass));
        commandsList.add(new Exp(conf, CommandGene.FloatClass));
        commandsList.add(new Pow(conf, CommandGene.FloatClass));
        commandsList.add(new Terminal(conf, CommandGene.FloatClass, minValue, maxValue, returnWholeNumbers));

        CommandGene[] commands = new CommandGene[commandsList.size()];
        commandsList.toArray(commands);


        //criação do nodeset
        CommandGene[][] nodeSets = new CommandGene[1][numVariables + commands.length];

        //cria e adiciona as variaveis no nodeSets.
        variables = new Variable[numVariables];
        int indexVariables = 0;
        for (int i = 0; i <= numVariables; i++) {

            String name = variableNames[i];
            if (i != outputVariable) {
                if (variableNames != null && variableNames.length > 0) {
                    name = variableNames[i];
                }
                variables[indexVariables] = new Variable(conf, name, CommandGene.FloatClass);
                nodeSets[0][indexVariables] = variables[indexVariables];
                System.out.println("Variáveis: " + variables[indexVariables]);
                indexVariables++;
            }
        }
        // adiciona no nodeSet as funcoes e terminais
        for (int i = 0; i < commands.length; i++) {
            System.out.println("function1: " + commands[i]);
            nodeSets[0][i + numVariables] = commands[i];
        }

        //inicia as variaveis X e Y para descobrir a função 4*sin(x);
        System.out.println("Fitiness Cases:");
        Random random = new Random();
        for (int i = 0; i < 40; i++) {
            float r = 4.0f * (random.nextFloat());

            x[i] = new Float(r);
            y[i] = (float) (4 * (Math.sin(r)));
            System.out.println(i + ") " + x[i] + "   " + y[i]);

        }
        System.out.println("");
        /*       
        conf - a configuração para usar. 
        
        types - o tipo de cada cromossomo, o comprimento é o número de cromossomos. 
        
        argTypes - os tipos de argumentos para cada cromossomo, 
        deve ser uma matriz de matrizes, a primeira dimensão do que é o número 
        de cromossomos ea segunda dimensão que é o número de argumentos para o cromossomo. 
        
        nodeSets - os nós que estão autorizados a ser utilizados por cada cromossomo,
        deve ser uma matriz de matrizes, a primeira dimensão do que é o número 
        de cromossomos ea segunda dimensão que é o número de nós. Note que não 
        é necessário incluir os argumentos de um cromossomo como terminais em 
        conjunto o cromossomo do nó. Isso é feito automaticamente. 
        
        maxNodes - reservar espaço para a_maxNodes número de nós. 
        
        verboseOutput - true: informação de saída de status para console. 
         */
        return GPGenotype.randomInitialGenotype(conf, types, argTypes, nodeSets, maxNodes, true);
    }

    public static void main(String[] args) throws InvalidConfigurationException {

        System.out.println("Fórmula a descobir: 4*sin(X) ");


        /*retirado do exemplo da função 4*sin(x) do site da api jgap
         * 
         * num_input_variables: 2
        variable_names: x y z
        output_variable: 2
        num_rows: 8
        functions: Multiply,Divide,Add,Subtract,Pow,Sqrt,Sine
        terminal_range: -10 10
        terminal_wholenumbers: true
        max_init_depth: 4
        population_size: 100
        max_crossover_depth: 4
        num_evolutions: 1800
        max_nodes: 21
         */
        variableNames = "X,Y".split(",");
        numVariables = 1;
        outputVariable = 1;
        maxNodes = 4000;
        maxValue = 100;
        minValue = -10;
        returnWholeNumbers = true;
        maxInitDepth = 10;
        populationSize = 100;
        maxCrossoverDepth = 30;
        numEvolutions =50;
        foundBeastIndiv= true;

        GPConfiguration config = new GPConfiguration();
        config.setCrossoverProb(crossoverProb);
        config.setMutationProb(mutationProb);
        config.setReproductionProb(reproductionProb);
        config.setMaxInitDepth(maxInitDepth);
        config.setMinInitDepth(minInitDepth);
        config.setPopulationSize(populationSize);
        config.setMaxCrossoverDepth(maxCrossoverDepth);
        config.setGPFitnessEvaluator(new DeltaGPFitnessEvaluator());

        tournamentSelectorSize = 20;
        if (tournamentSelectorSize > 0) {
            config.setSelectionMethod(new TournamentSelector(tournamentSelectorSize));
        }


        config.setFitnessFunction(new CurvaProblemv2.FuncaoFitness());
        config.setStrictProgramCreation(true);

        GPProblem problem = new CurvaProblemv2(config);


        // Create the genotype of the problem, i.e., define the GP commands and
        // terminals that can be used, and constrain the structure of the GP
        // program.
        // --------------------------------------------------------------------
        long start = System.currentTimeMillis();



        GPGenotype gp = problem.create();
        //mostra as informações do status no console.
        gp.setVerboseOutput(true);
        
        double bestFitness = Double.MAX_VALUE;
        String bestIndv;
        for(int generation = 0;generation<numEvolutions;generation++){
            System.out.println("Geração:"+ generation);
            gp.evolve();//evolui uma população
            gp.calcFitness();//calcula o fitness de todas os indivíduos presentes na população
            
            GPPopulation population = gp.getGPPopulation();
            
            //Determina mais apito o mais apto da população (aquele com o melhor valor de fitness)
            IGPProgram fittest = population.determineFittestProgram();
            
            ProgramChromosome chrom = fittest.getChromosome(0);
            String indv = chrom.toStringNorm(0);
            double fitnes = fittest.getFitnessValue();
            
//            System.out.println("Melhor Indivíduo: " +indv);
//            System.out.println("Valor do Fitnes: " +fitnes);
//                   
            if(fitnes<bestFitness){
                    bestFitness = fitnes;
                    bestIndv = indv;
                    System.out.println("Geração:" + generation);
                    System.out.println("Melhor Indivíduo: " +indv);
                    System.out.println("Valor do Fitnes: " +fitnes);
                    System.out.println("Profundidade:"+ chrom.getDepth(0));
                }
            if(foundBeastIndiv){
                if(bestFitness == 0){
                    System.out.println("Geração:"+generation);
                    System.out.println("Indivíduo Perfeito");
                    System.out.println("Parando a geração");
                    break;
                }

            }
            
            
        }
        gp.outputSolution(gp.getAllTimeBest());
        
//        gp.evolve(numEv
        
        //gp.outputSolution(gp.getAllTimeBest());
        
//        gp.evolve(numEvolutions);
//        gp.outputSolution(gp.getAllTimeBest());
        long finish = System.currentTimeMillis();
        long total = finish - start;
        String time = String.format("%5.2f", (finish - start)/1000.0f);
        System.out.println("Tempo: "+(total/1000)+" segundos.");

        problem.showTree(gp.getAllTimeBest(), "mathproblem_best6.png");

    }

    public static class FuncaoFitness extends GPFitnessFunction {

        private static final long serialVersionUID = 1L;

        @Override
        protected double evaluate(IGPProgram igpp) {
            return rawFitness(igpp);
        }

        private double rawFitness(IGPProgram indv) {
            Object[] noargs = new Object[0];
            Double error = 0.0;

            for (int i = 0; i < 40; i++) {
                variables[0].set(x[i]);

                try {

                    double result = indv.execute_float(0, noargs);

                    error += Math.abs(result - y[i]);

                    if (Double.isInfinite(result)) {

                        return Double.MAX_VALUE;
                    }

                } catch (ArithmeticException ex) {
                    System.out.println("x = " + x[i].floatValue());
                    System.out.println(indv);
                    throw ex;
                }
            }
            if (error < 0.001) {
                error = 0.0d;
            }
            return error;
        }
    }
}
